/*
 * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Project.xs contains XS wrappers for the project database maniplulation
 * functions as provided by libproject and described in getprojent(3EXACCT).
 */

/* Solaris includes. */
#include <zone.h>
#include <project.h>
#include <pool.h>
#include <sys/pool_impl.h>
#include <rctl.h>
#include <stdio.h>

/* Perl includes. */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/*
 * Convert and save a struct project on the perl XS return stack.
 * In a void context it returns nothing, in a scalar context it returns just
 * the name of the project and in a list context it returns a 6-element list
 * consisting of (name, projid, comment, users, groups, attr), where users and
 * groups are references to arrays containing the appropriate lists.
 */
static int
pushret_project(const struct project *proj)
{
	char	**cp;
	AV	*ary;

	dSP;
	if (GIMME_V == G_SCALAR) {
		EXTEND(SP, 1);
		PUSHs(sv_2mortal(newSVpv(proj->pj_name, 0)));
		PUTBACK;
		return (1);
	} else if (GIMME_V == G_ARRAY) {
		EXTEND(SP, 6);
		PUSHs(sv_2mortal(newSVpv(proj->pj_name, 0)));
		PUSHs(sv_2mortal(newSViv(proj->pj_projid)));
		PUSHs(sv_2mortal(newSVpv(proj->pj_comment, 0)));
		ary = newAV();
		for (cp = proj->pj_users; *cp != NULL; cp++) {
			av_push(ary, newSVpv(*cp, 0));
		}
		PUSHs(sv_2mortal(newRV_noinc((SV *)ary)));
		ary = newAV();
		for (cp = proj->pj_groups; *cp != NULL; cp++) {
			av_push(ary, newSVpv(*cp, 0));
		}
		PUSHs(sv_2mortal(newRV_noinc((SV *)ary)));
		PUSHs(sv_2mortal(newSVpv(proj->pj_attr, 0)));
		PUTBACK;
		return (6);
	} else {
		return (0);
	}
}

static int
pwalk_cb(const projid_t project, void *walk_data)
{
	int *nitemsp;

	dSP;
	nitemsp = (int *) walk_data;
	EXTEND(SP, 1);
	PUSHs(sv_2mortal(newSViv(project)));
	(*nitemsp)++;
	PUTBACK;
	return (0);
}

/*
 * The XS code exported to perl is below here.  Note that the XS preprocessor
 * has its own commenting syntax, so all comments from this point on are in
 * that form.  Note also that the PUTBACK; lines are necessary to synchronise
 * the local and global views of the perl stack before calling pushret_project,
 * as the code generated by the perl XS compiler twiddles with the stack on
 * entry to an XSUB.
 */

MODULE = Sun::Solaris::Project PACKAGE = Sun::Solaris::Project
PROTOTYPES: ENABLE

 #
 # Define any constants that need to be exported.  By doing it this way we can
 # avoid the overhead of using the DynaLoader package, and in addition constants
 # defined using this mechanism are eligible for inlining by the perl
 # interpreter at compile time.
 #
BOOT:
	{
	HV *stash;
	char buf[128];
	stash = gv_stashpv("Sun::Solaris::Project", TRUE);
	newCONSTSUB(stash, "MAXPROJID", newSViv(MAXPROJID));
	newCONSTSUB(stash, "PROJNAME_MAX", newSViv(PROJNAME_MAX));
	newCONSTSUB(stash, "PROJF_PATH",
	    newSVpv(PROJF_PATH, sizeof (PROJF_PATH) - 1));
	newCONSTSUB(stash, "PROJECT_BUFSZ", newSViv(PROJECT_BUFSZ));
	newCONSTSUB(stash, "SETPROJ_ERR_TASK", newSViv(SETPROJ_ERR_TASK));
	newCONSTSUB(stash, "SETPROJ_ERR_POOL", newSViv(SETPROJ_ERR_POOL));
	newCONSTSUB(stash, "RCTL_GLOBAL_NOBASIC",
		newSViv(RCTL_GLOBAL_NOBASIC));
	newCONSTSUB(stash, "RCTL_GLOBAL_LOWERABLE",
		newSViv(RCTL_GLOBAL_LOWERABLE));
	newCONSTSUB(stash, "RCTL_GLOBAL_DENY_ALWAYS",
		newSViv(RCTL_GLOBAL_DENY_ALWAYS));
	newCONSTSUB(stash, "RCTL_GLOBAL_DENY_NEVER",
		newSViv(RCTL_GLOBAL_DENY_NEVER));
	newCONSTSUB(stash, "RCTL_GLOBAL_FILE_SIZE",
		newSViv(RCTL_GLOBAL_FILE_SIZE));
	newCONSTSUB(stash, "RCTL_GLOBAL_CPU_TIME",
		newSViv(RCTL_GLOBAL_CPU_TIME));
	newCONSTSUB(stash, "RCTL_GLOBAL_SIGNAL_NEVER",
		newSViv(RCTL_GLOBAL_SIGNAL_NEVER));
	newCONSTSUB(stash, "RCTL_GLOBAL_INFINITE",
		newSViv(RCTL_GLOBAL_INFINITE));
	newCONSTSUB(stash, "RCTL_GLOBAL_UNOBSERVABLE",
		newSViv(RCTL_GLOBAL_UNOBSERVABLE));
	newCONSTSUB(stash, "RCTL_GLOBAL_BYTES",
		newSViv(RCTL_GLOBAL_BYTES));
	newCONSTSUB(stash, "RCTL_GLOBAL_SECONDS",
		newSViv(RCTL_GLOBAL_SECONDS));
	newCONSTSUB(stash, "RCTL_GLOBAL_COUNT",
		newSViv(RCTL_GLOBAL_COUNT));
	sprintf(buf, "%llu", UINT64_MAX);
	newCONSTSUB(stash, "RCTL_MAX_VALUE",
		newSVpv(buf, strlen(buf)));
	}

projid_t
getprojid()

int
setproject(name, user_name, flags)
	const char	*name;
	const char	*user_name
	uint_t		flags

void
activeprojects()
PREINIT:
	int	nitems;
PPCODE:
	PUTBACK;
	nitems = 0;
	project_walk(&pwalk_cb, (void*)&nitems);
	XSRETURN(nitems);

void
getprojent()
PREINIT:
	struct project	proj, *projp;
	char		buf[PROJECT_BUFSZ];
PPCODE:
	PUTBACK;
	if (projp = getprojent(&proj, buf, sizeof (buf))) {
		XSRETURN(pushret_project(projp));
	} else {
		XSRETURN_EMPTY;
	}

void
setprojent()

void
endprojent()

void
getprojbyname(name)
	char	*name
PREINIT:
	struct project	proj, *projp;
	char		buf[PROJECT_BUFSZ];
PPCODE:
	PUTBACK;
	if (projp = getprojbyname(name, &proj, buf, sizeof (buf))) {
		XSRETURN(pushret_project(projp));
	} else {
		XSRETURN_EMPTY;
	}

void
getprojbyid(id)
	projid_t	id
PREINIT:
	struct project	proj, *projp;
	char		buf[PROJECT_BUFSZ];
PPCODE:
	PUTBACK;
	if (projp = getprojbyid(id, &proj, buf, sizeof (buf))) {
		XSRETURN(pushret_project(projp));
	} else {
		XSRETURN_EMPTY;
	}

void
getdefaultproj(user)
	char	*user
PREINIT:
	struct project	proj, *projp;
	char		buf[PROJECT_BUFSZ];
PPCODE:
	PUTBACK;
	if (projp = getdefaultproj(user, &proj, buf, sizeof (buf))) {
		XSRETURN(pushret_project(projp));
	} else {
		XSRETURN_EMPTY;
	}

void
fgetprojent(fh)
	FILE	*fh
PREINIT:
	struct project	proj, *projp;
	char		buf[PROJECT_BUFSZ];
PPCODE:
	PUTBACK;
	if (projp = fgetprojent(fh, &proj, buf, sizeof (buf))) {
		XSRETURN(pushret_project(projp));
	} else {
		XSRETURN_EMPTY;
	}

bool
inproj(user, proj)
	char	*user
	char	*proj
PREINIT:
	char	buf[PROJECT_BUFSZ];
CODE:
	RETVAL = inproj(user, proj, buf, sizeof (buf));


int
getprojidbyname(proj)
	char	*proj
PREINIT:
	int	id;
PPCODE:
	if ((id = getprojidbyname(proj)) == -1) {
		XSRETURN_UNDEF;
	} else {
		XSRETURN_IV(id);
	}


# rctl_get_info(name)
#
# For the given rctl name, returns the list
# ($max, $flags), where $max is the integer value
# of the system rctl, and $flags are the rctl's
# global flags, as returned by rctlblk_get_global_flags
#
# This function is private to Project.pm
void
rctl_get_info(name)
	char	*name
PREINIT:
	rctlblk_t *blk1 = NULL;
	rctlblk_t *blk2 = NULL;
	rctlblk_t *tmp = NULL;
	rctl_priv_t priv;
	rctl_qty_t value;
	int flags;
	int ret;
	int err = 0;
	char string[24];	/* 24 will always hold a uint64_t */
PPCODE:
	Newc(0, blk1, rctlblk_size(), char, rctlblk_t);
	if (blk1 == NULL) {
		err = 1;
		goto out;
	}
	Newc(1, blk2, rctlblk_size(), char, rctlblk_t);
	if (blk2 == NULL) {
		err = 1;
		goto out;
	}
	ret = getrctl(name, NULL, blk1, RCTL_FIRST);
	if (ret != 0) {
		err = 1;
		goto out;
	}
	priv = rctlblk_get_privilege(blk1);
	while (priv != RCPRIV_SYSTEM) {
		tmp = blk2;
		blk2 = blk1;
		blk1 = tmp;
		ret = getrctl(name, blk2, blk1, RCTL_NEXT);
		if (ret != 0) {
			err = 1;
			goto out;
		}
		priv = rctlblk_get_privilege(blk1);
	}
	value = rctlblk_get_value(blk1);
	flags = rctlblk_get_global_flags(blk1);
	ret = sprintf(string, "%llu", value);
	if (ret <= 0) {
		err = 1;
	}
	out:
	if (blk1)
		Safefree(blk1);
	if (blk2)
		Safefree(blk2);
	if (err)
		XSRETURN(0);

	XPUSHs(sv_2mortal(newSVpv(string, 0)));
	XPUSHs(sv_2mortal(newSViv(flags)));
	XSRETURN(2);

#
# pool_exists(name)
#
# Returns 0 a pool with the given name exists on the current system.
# Returns 1 if pools are disabled or the pool does not exist
#
# Used internally by project.pm to validate the project.pool attribute
#
# This function is private to Project.pm
void
pool_exists(name)
	char	*name
PREINIT:
	pool_conf_t *conf;
	pool_t *pool;
	pool_status_t status;
	int fd;
PPCODE:

	/*
	 * Determine if pools are enabled using /dev/pool directly, as
	 * libpool may not be present.
	 */
	if (getzoneid() != GLOBAL_ZONEID) {
		XSRETURN_IV(1);
	}
	if ((fd = open("/dev/pool", O_RDONLY)) < 0) {
		XSRETURN_IV(1);
	}
	if (ioctl(fd, POOL_STATUSQ, &status) < 0) {
		(void) close(fd);
		XSRETURN_IV(1);
	}
	close(fd);
	if (status.ps_io_state != 1) {
		XSRETURN_IV(1);
	}

	/*
	 * If pools are enabled, assume libpool is present.
	 */
	conf = pool_conf_alloc();
	if (conf == NULL) {
		XSRETURN_IV(1);
	}
	if (pool_conf_open(conf, pool_dynamic_location(), PO_RDONLY)) {
		pool_conf_free(conf);
		XSRETURN_IV(1);
	}
	pool = pool_get_pool(conf, name);
	if (pool == NULL) {
		pool_conf_close(conf);
		pool_conf_free(conf);
		XSRETURN_IV(1);
	}
	pool_conf_close(conf);
	pool_conf_free(conf);
	XSRETURN_IV(0);

